import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 * A ciphertext object contains details of the ciphertext to be attacked.
 * 
 * @author Tom Castle
 * @version v.0.01 16/02/04
 */
public class CipherText
{
    // instance variables - replace the example below with your own
    private String cipherText, startCipherText;
    private ArrayList cipherWords, startCipherWords;

    /**
     * Constructor for objects of class CipherText
     * 
     * @param ciphertext to assign object (can be changed).
     */
    public CipherText(String cipherText)
    {
        setCipherText(cipherText);
        cipherWords = new ArrayList();
        startCipherWords = new ArrayList();
    }

    /**
     * Constructor for objects of class CipherText
     */
    public CipherText()
    {
        cipherText = "";
        cipherWords = new ArrayList();
        startCipherWords = new ArrayList();
    }

    /**
     * Returns the ciphertext.
     * 
     * @return     ciphertext string.
     */
    public String getCipherText()
    {
        return cipherText;
    }
    
    /**
     * Returns the first 35 words of ciphertext as an ArrayList.
     * CHANGE TO x NUMBER OF WORDS TAKEN FROM OPTIONS VARIABLE.
     * 
     * @return CipherText
     */
    public ArrayList getStartCipherWords()
    {     
        return startCipherWords;
    }
    
    /**
     * Finds the first x words of ciphertext, and inserts into 
     * startCipherWords variable.
     */
    public ArrayList findStartCipherWords(int x)
    {
        findCipherWords();
        int i = 0;
        Iterator it = cipherWords.iterator();
        while(it.hasNext() && i<x){
            String thisWord = (String) it.next();
            startCipherWords.add(thisWord);
            i++;
        }
        
        return removeDupes(startCipherWords);
    }
    
    /**
     * Sets the ciphertext to the value of the parameter.
     *
     * @param   ciphertext string. 
     */
    public void setCipherText(String cipherText)
    {
        this.cipherText = stripPunc(cipherText.toUpperCase());

    }
    
    /**
     * Returns the arraylist containing the parsed words
     * of the ciphertext.
     * 
     * @return  ciphertext words in an arraylist.
     */
    public ArrayList getCipherWords()
    {
        return cipherWords;
    }
    
    /**
     * If no-param, use all of this current cipherText.
     */
    public ArrayList findCipherWords()
    {
        return findCipherWords(cipherText);
    }
    
    /**
     * If only cipherText, use all of given text
     */
    public ArrayList findCipherWords(String cipherText)
    {
        return findCipherWords(cipherText,0);
    }
    
    /**
     * Create an arraylist of all the cipher text's words.
     */
    public ArrayList findCipherWords(String cipherText, int noWords)
    {
        StringTokenizer tokenizer = new StringTokenizer(cipherText);
        cipherWords.clear();
        
        while (tokenizer.hasMoreTokens() || noWords != 0){
            cipherWords.add(tokenizer.nextToken());
        }
        
        return cipherWords;
    }
    
    /**
     * Removes the duplicates from a given list, and returns the new list.
     */
    private ArrayList removeDupes(ArrayList cipherWords)
    {
        Iterator it = cipherWords.iterator();
        while (it.hasNext()){
            String word = (String) it.next();
            if (cipherWords.indexOf(word) != cipherWords.lastIndexOf(word)){
                it.remove();
            }
        }
        return cipherWords;
    }
    
    /**
     * Decrypt cipherText using given decryption permutation.
     */
    public String decrypt(char[] fd)
    {
        String plainText = "";
        
        char cChar;
        int fdPos;
        int cLength = cipherText.length();
        for (int i=0; i < cLength; i++){
            cChar = cipherText.charAt(i);
            fdPos = ((int) cChar) - 65;
            //If non-letter then add straight to plaintext.
            if (fdPos >= 0 && fdPos < 26)
                plainText += fd[fdPos];
            else
                plainText += cChar;
        }
        
        return plainText;
    }

    /**
     * Calculate and return the index of coincidence.
     * 
     * @return the index of coincidence of this ciphertext.
     */
    public double getIOC()
    {
        return indexCalculator(cipherText);
        /*
        double numerator = 0;
        double charFreq;
        double index;
        char cipherChar;
        int[] arrFreq = getCharFreqArray();
        for (int i=0; i<26; i++){
            
            cipherChar = (char) (i+65);
            charFreq = arrFreq[((int) cipherChar)-65];
            numerator += (charFreq * (charFreq-1));
        }
        int ctLength = cipherText.length();
        index = numerator / (ctLength * (ctLength-1));
        return index;*/
    }
    
    /**
     * Calculate IOC
     */
    //a boolean method to test if a character is a letter
    public boolean isLetter( char letter ){
       return ( (('a' <= letter) && (letter <= 'z') ) || (('A' <= letter) && (letter <= 'Z')));
    }
    
    //strip a String of all but its legitimate letter characters
    public String strip(String word) {
       int numLetters = 0, n = 0;
       String temp = word.toLowerCase();
       char[] letters = new char[temp.length()];
       char next;
       //find the legitimate letters and save them.
       while (n < temp.length() ) {
          next = temp.charAt(n++);
          if (isLetter( next ) )
             letters[numLetters++]=next;
       }
       return String.copyValueOf(letters,0,numLetters);
    }
    
    //the IC estimator method
    public float indexCalculator(String X) {
       String Y = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
       X = strip(X);//to lowercase and delete garbage chars
       Y = strip(Y);
       // get the length of the shortest string
       int totalLength = X.length();
 	  if( Y.length() < totalLength )
 		 totalLength = Y.length();
 	
 	  // quit if there are no characters to work with
 	  if( totalLength == 0 )
 		 return (float) 0.0;
 		
 	  // count the number of chars that are the same
 	  int sameChars = 0;
 	  for( int i = 0; i < totalLength; ++i )
 			if( X.charAt(i) == Y.charAt(i) )
 				++sameChars;
 		
 	  // calculate the ioc and return it
 	  float ioc = (float)sameChars / (float)totalLength;
 	  return ioc;
    }

    
    /**
     * Remove all non-letters.
     * @param The string to be cleaned
     * @return Clean string.
     */
	public String cleanString(String toClean)
	{
		String returnString = new String();

		for(int i=0;i<toClean.length();i++)
		{
			if (Character.isLetter(toClean.charAt(i)) == true)
				returnString = returnString + toClean.charAt(i);
		}

		return returnString;
	}
    
    /**
     * Create & populate character frequency array.
     */
    private int[] getCharFreqArray()
    {
        int c;
        int i = 0;
        int[] ctFreq = new int[26];
        while (i < cipherText.length()){
            // Convert to an integer index for our array ctFreq
            c = ((int) cipherText.charAt(i))-65;
            // If an (uppercase) letter.
            if (!(c < 0 || c > 25)){
                ctFreq[c]++;
            }
            i++;
        }
        return ctFreq;
    }
    
    /**
     * Overloads getLanguage, calling with a calculated index.
     */
    public String getLanguage()
    {
        return getLanguage(getIOC());
    }
    
    /**
     * Use the given index of coincidence to work out the language.
     * 
     * @param Double - Index of Coincidence
     * @return String language
     */
    public String getLanguage(double index)
    {
        if (index < 0)
            return "Unknown";
        if (index < 0.05)
            return "Random text string";
        if (index < 0.071)
            return "English";
        if (index < 0.85)
            return "French";
        else
            return "Unknown";
    }
    
    /**
     * Removes non-letters from ciphertext.
     */
    private String stripPunc(String cipherText)
    {
        String newCipherText = "";
        int len = cipherText.length();
        char cipherChar;
        for (int i=0; i<len; i++){
            cipherChar = cipherText.charAt(i);
            if ((cipherChar >= 'A' && cipherChar <= 'Z') || cipherChar == ' ')
                newCipherText += cipherChar;
        }
        return newCipherText;
    }
}
